package com.hujie.sqlqueryes.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.elasticsearch.indices.get_mapping.IndexMappingRecord;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hujie.sqlqueryes.dto.DslResultDTO;
import com.hujie.sqlqueryes.service.impl.DslExecutionServiceImpl;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ElasticsearchService {
    
    private static final Logger logger = LoggerFactory.getLogger(ElasticsearchService.class);

    // 注入连接管理器
    @Autowired
    private ElasticsearchConnectionManager connectionManager;
    
    private ObjectMapper objectMapper = new ObjectMapper();
    
    @Autowired
    private SqlToDslConverterService sqlToDslConverterService;
    
    @Autowired
    private DslExecutionServiceImpl dslExecutionService;

    /**
     * 为指定用户建立Elasticsearch连接
     *
     * @param userId 用户唯一标识符
     * @param host   Elasticsearch主机地址
     * @param port   Elasticsearch端口号
     * @return 是否连接成功
     */
    public boolean connect(String userId, String host, int port) {
        return connectionManager.connectUser(userId, host, port);
    }
    
    /**
     * 断开指定用户的Elasticsearch连接
     *
     * @param userId 用户唯一标识符
     */
    public void disconnect(String userId) {
        connectionManager.disconnectUser(userId);
    }
    
    /**
     * 检查指定用户是否已连接
     *
     * @param userId 用户唯一标识符
     * @return 是否已连接
     */
    public boolean isConnected(String userId) {
        return connectionManager.isConnected(userId);
    }
    
    /**
     * 检查当前用户是否已连接
     *
     * @return 是否已连接
     */
    public boolean isCurrentConnected() {
        return connectionManager.isCurrentConnected();
    }

    public List<String> getAllIndices() throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }

        List<String> indices = new ArrayList<>();
        client.cat().indices().valueBody().forEach(index -> {
            String indexName = index.index();
            if (indexName != null && !indexName.startsWith(".")) {
                indices.add(indexName);
            }
        });

        return indices;
    }

    public boolean createIndex(String indexName) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }
        
        // 处理包含特殊字符的索引名称
        String processedIndexName = processIndexName(indexName);
        
        // 检查索引是否已存在
        boolean exists = client.indices().exists(e -> e.index(processedIndexName)).value();
        if (exists) {
            return false; // 索引已存在
        }
        
        // 创建索引
        CreateIndexRequest request = new CreateIndexRequest.Builder()
                .index(processedIndexName)
                .build();
        
        CreateIndexResponse response = client.indices().create(request);
        return response.acknowledged();
    }
    
    public boolean createIndexWithMapping(String indexName, String mapping) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }
        
        // 处理包含特殊字符的索引名称
        String processedIndexName = processIndexName(indexName);
        
        // 检查索引是否已存在
        boolean exists = client.indices().exists(e -> e.index(processedIndexName)).value();
        if (exists) {
            return false; // 索引已存在
        }
        
        // 创建带有映射的索引
        CreateIndexRequest request = new CreateIndexRequest.Builder()
                .index(processedIndexName)
                .withJson(new java.io.StringReader(mapping))
                .build();
        
        CreateIndexResponse response = client.indices().create(request);
        return response.acknowledged();
    }
    
    public boolean deleteIndex(String indexName) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }
        
        // 处理包含特殊字符的索引名称
        String processedIndexName = processIndexName(indexName);
        
        // 删除索引
        DeleteIndexRequest request = new DeleteIndexRequest.Builder()
                .index(processedIndexName)
                .build();
        
        return client.indices().delete(request).acknowledged();
    }

    /**
     * 获取索引的映射信息
     * @param indexName 索引名称
     * @return 索引映射信息
     * @throws IOException IO异常
     */
    public Map<String, Object> getIndexMapping(String indexName) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }
        
        logger.info("获取索引 {} 的映射信息", indexName);
        
        // 处理包含特殊字符的索引名称
        String processedIndexName = processIndexName(indexName);
        
        // 获取索引映射
        GetMappingResponse mappingResponse = client.indices().getMapping(m -> m.index(processedIndexName));
        
        // 转换为Map格式返回
        Map<String, Object> result = new HashMap<>();
        if (mappingResponse.result().containsKey(processedIndexName)) {
            TypeMapping typeMapping = mappingResponse.result().get(processedIndexName).mappings();
            Map<String, Object> mappingsMap = new HashMap<>();
            
            // 将TypeMapping转换为Map
            if (typeMapping.properties() != null) {
                Map<String, Object> simplifiedProperties = simplifyProperties(typeMapping.properties());
                mappingsMap.put("properties", simplifiedProperties);
            }
            
            result.put("mappings", mappingsMap);
            logger.info("成功获取索引 {} 的映射信息", indexName);
            logger.debug("索引 {} 的映射信息内容: {}", indexName, mappingsMap);
        } else {
            // 尝试其他可能的键名
            boolean found = false;
            for (Map.Entry<String, IndexMappingRecord> entry : mappingResponse.result().entrySet()) {
                if (entry.getKey().contains(indexName)) {
                    TypeMapping typeMapping = entry.getValue().mappings();
                    Map<String, Object> mappingsMap = new HashMap<>();
                    
                    // 将TypeMapping转换为Map
                    if (typeMapping.properties() != null) {
                        Map<String, Object> simplifiedProperties = simplifyProperties(typeMapping.properties());
                        mappingsMap.put("properties", simplifiedProperties);
                    }
                    
                    result.put("mappings", mappingsMap);
                    logger.info("成功获取索引 {} 的映射信息 (使用匹配键名: {})", indexName, entry.getKey());
                    logger.debug("索引 {} 的映射信息内容: {}", indexName, mappingsMap);
                    found = true;
                    break;
                }
            }
            if (!found) {
                result.put("mappings", new HashMap<>());
                logger.warn("索引 {} 没有找到映射信息", indexName);
            }
        }
        return result;
    }
    
    /**
     * 获取索引的设置信息
     * @param indexName 索引名称
     * @return 索引设置信息
     * @throws IOException IO异常
     */
    public Map<String, Object> getIndexSettings(String indexName) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }
        
        logger.info("获取索引 {} 的设置信息", indexName);
        
        // 处理包含特殊字符的索引名称
        String processedIndexName = processIndexName(indexName);
        
        // 获取索引设置
        GetIndicesSettingsResponse settingsResponse = client.indices().getSettings(s -> s.index(processedIndexName));
        
        // 转换为Map格式返回
        Map<String, Object> result = new HashMap<>();
        if (settingsResponse.result().containsKey(processedIndexName)) {
            result.put("settings", settingsResponse.result().get(processedIndexName).settings());
            logger.info("成功获取索引 {} 的设置信息", indexName);
            logger.debug("索引 {} 的设置信息内容: {}", indexName, settingsResponse.result().get(processedIndexName).settings());
        } else {
            // 尝试其他可能的键名
            boolean found = false;
            for (Map.Entry<String, IndexState> entry : settingsResponse.result().entrySet()) {
                if (entry.getKey().contains(indexName)) {
                    result.put("settings", entry.getValue().settings());
                    logger.info("成功获取索引 {} 的设置信息 (使用匹配键名: {})", indexName, entry.getKey());
                    logger.debug("索引 {} 的设置信息内容: {}", indexName, entry.getValue().settings());
                    found = true;
                    break;
                }
            }
            
            if (!found) {
                result.put("settings", new HashMap<>());
                logger.warn("索引 {} 没有找到设置信息", indexName);
            }
        }
        return result;
    }

    /**
     * 刷新索引，使新插入的文档可以被搜索到
     * @param indexName 索引名称
     * @throws IOException IO异常
     */
    public void refreshIndex(String indexName) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }
        
        // 处理包含特殊字符的索引名称
        String processedIndexName = processIndexName(indexName);
        
        // 刷新索引
        client.indices().refresh(r -> r.index(processedIndexName));
    }

    public Map<String, Object> getIndexData(String indexName, int from, int size) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }
        
        // 处理包含特殊字符的索引名称
        String processedIndexName = processIndexName(indexName);
        
        // 构建搜索请求
        SearchRequest searchRequest = SearchRequest.of(s -> s
                .index(processedIndexName)
                .from(from)
                .size(size)
                .query(q -> q.matchAll(m -> m))
        );
        
        // 执行搜索
        SearchResponse<Map> searchResponse = client.search(searchRequest, Map.class);
        
        // 构建返回结果
        Map<String, Object> result = new HashMap<>();
        result.put("took", searchResponse.took());
        result.put("timed_out", searchResponse.timedOut());
        result.put("total", searchResponse.hits().total().value());
        
        List<Map<String, Object>> hits = new ArrayList<>();
        for (Hit<Map> hit : searchResponse.hits().hits()) {
            Map<String, Object> hitData = new HashMap<>();
            hitData.put("id", hit.id());
            hitData.put("source", hit.source());
            hitData.put("score", hit.score());
            hits.add(hitData);
        }
        
        result.put("hits", hits);
        return result;
    }

    public Map<String, Object> executeSqlQuery(String sql) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            logger.warn("Elasticsearch客户端未连接");
            throw new IOException("Elasticsearch client not connected for current user");
        }
        
        logger.info("执行SQL查询: {}", sql);

        // 将SQL转换为DSL
        DslResultDTO dslResult = sqlToDslConverterService.convertSqlToDsl(sql);
        if (!dslResult.isSuccess()) {
            logger.error("SQL转换DSL失败: {}", dslResult.getMessage());
            throw new IOException("SQL转换DSL失败: " + dslResult.getMessage());
        }
        
        logger.debug("转换后的DSL: {}", dslResult.getDsl());
        
        // 根据DSL操作类型执行相应的Elasticsearch操作
        Map<String, Object> result = dslExecutionService.executeDslOperation(dslResult.getDsl());
        logger.info("SQL查询执行完成");
        return result;
    }

    public Map<String, Object> executeQuery(String sql) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }

        // 将SQL转换为DSL
        DslResultDTO dslResult = sqlToDslConverterService.convertSqlToDsl(sql);
        if (!dslResult.isSuccess()) {
            throw new IOException("SQL转换DSL失败: " + dslResult.getMessage());
        }
        
        // 根据DSL操作类型执行相应的Elasticsearch操作
        return dslExecutionService.executeDslOperation(dslResult.getDsl());
    }
    
    /**
     * 直接执行DSL查询
     * @param dsl DSL查询语句
     * @return 查询结果
     * @throws IOException IO异常
     */
    public Map<String, Object> executeDslQuery(String dsl) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }
        
        logger.info("执行DSL查询: {}", dsl);
        
        try {
            // 根据DSL操作类型执行相应的Elasticsearch操作
            Map<String, Object> result = dslExecutionService.executeRawDslQuery(dsl);
            logger.info("DSL查询执行完成");
            return result;
        } catch (Exception e) {
            logger.error("DSL查询执行失败: {}", e.getMessage(), e);
            throw new IOException("DSL查询执行失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 更新索引映射，添加新字段
     * @param indexName 索引名称
     * @param mappingProperties 新的映射属性
     * @return 是否更新成功
     * @throws IOException IO异常
     */
    public boolean updateIndexMapping(String indexName, Map<String, Object> mappingProperties) throws IOException {
        ElasticsearchClient client = connectionManager.getCurrentClient();
        if (client == null) {
            throw new IOException("Elasticsearch client not connected for current user");
        }
        
        logger.info("更新索引 {} 的映射", indexName);
        
        // 处理包含特殊字符的索引名称
        String processedIndexName = processIndexName(indexName);
        
        // 构建映射更新请求
        Map<String, Object> mapping = new HashMap<>();
        mapping.put("properties", mappingProperties);
        
        // 创建PutMappingRequest
        PutMappingRequest request = new PutMappingRequest.Builder()
                .index(processedIndexName)
                .properties(convertToPropertiesMap(mappingProperties))
                .build();
        
        // 执行更新映射操作
        PutMappingResponse response = client.indices().putMapping(request);
        logger.info("索引 {} 映射更新完成", indexName);
        return response.acknowledged();
    }
    
    /**
     * 将 Map<String, Object> 转换为 Map<String, Property>
     * @param propertiesMap 包含属性定义的Map
     * @return 转换后的Property映射
     */
    @SuppressWarnings("unchecked")
    private Map<String, Property> convertToPropertiesMap(Map<String, Object> propertiesMap) {
        Map<String, Property> properties = new HashMap<>();
        
        for (Map.Entry<String, Object> entry : propertiesMap.entrySet()) {
            String fieldName = entry.getKey();
            Object fieldConfig = entry.getValue();
            
            if (fieldConfig instanceof Map) {
                Map<String, Object> configMap = (Map<String, Object>) fieldConfig;
                String type = (String) configMap.get("type");
                
                if (type != null) {
                    // 根据类型创建相应的Property
                    switch (type) {
                        case "text":
                            properties.put(fieldName, Property.of(p -> p.text(t -> {
                                String analyzer = (String) configMap.get("analyzer");
                                if (analyzer != null) {
                                    t.analyzer(analyzer);
                                }
                                return t;
                            })));
                            break;
                        case "keyword":
                            properties.put(fieldName, Property.of(p -> p.keyword(k -> k)));
                            break;
                        case "integer":
                            properties.put(fieldName, Property.of(p -> p.integer(i -> i)));
                            break;
                        case "long":
                            properties.put(fieldName, Property.of(p -> p.long_(l -> l)));
                            break;
                        case "date":
                            properties.put(fieldName, Property.of(p -> p.date(d -> {
                                String format = (String) configMap.get("format");
                                if (format != null) {
                                    d.format(format);
                                }
                                return d;
                            })));
                            break;
                        case "boolean":
                            properties.put(fieldName, Property.of(p -> p.boolean_(b -> b)));
                            break;
                        case "scaled_float":
                            properties.put(fieldName, Property.of(p -> p.scaledFloat(s -> {
                                Object scalingFactor = configMap.get("scaling_factor");
                                if (scalingFactor instanceof Number) {
                                    s.scalingFactor(((Number) scalingFactor).doubleValue());
                                }
                                return s;
                            })));
                            break;
                        default:
                            // 默认使用文本类型
                            properties.put(fieldName, Property.of(p -> p.text(t -> t)));
                            break;
                    }
                } else {
                    // 如果没有指定类型，默认使用文本类型
                    properties.put(fieldName, Property.of(p -> p.text(t -> t)));
                }
            } else {
                // 如果配置不是Map，默认使用文本类型
                properties.put(fieldName, Property.of(p -> p.text(t -> t)));
            }
        }
        
        return properties;
    }
    
    /**
     * 处理索引名称，如果包含特殊字符（如连字符）则去除方括号
     * @param indexName 原始索引名称
     * @return 处理后的索引名称
     */
    private String processIndexName(String indexName) {
        // 检查索引名称是否被方括号包装
        if (indexName != null && indexName.startsWith("[") && indexName.endsWith("]")) {
            // 去除方括号
            return indexName.substring(1, indexName.length() - 1);
        }
        return indexName;
    }
    
    /**
     * 简化属性信息，只返回字段名和对应的类型
     * @param properties 原始属性映射
     * @return 简化后的属性映射
     */
    private Map<String, Object> simplifyProperties(Map<String, Property> properties) {
        Map<String, Object> simplified = new HashMap<>();
        
        for (Map.Entry<String, Property> entry : properties.entrySet()) {
            String fieldName = entry.getKey();
            Property property = entry.getValue();
            
            try {
                // 提取字段类型
                String fieldType = getPropertyType(property);
                Map<String, Object> fieldInfo = new HashMap<>();
                fieldInfo.put("type", fieldType);
                
                // 如果是text类型，可能还有analyzer等属性
                if ("text".equals(fieldType) && property.text() != null) {
                    if (property.text().analyzer() != null) {
                        fieldInfo.put("analyzer", property.text().analyzer());
                    }
                    if (property.text().fields() != null && !property.text().fields().isEmpty()) {
                        Map<String, Object> subFields = new HashMap<>();
                        for (Map.Entry<String, Property> subField : property.text().fields().entrySet()) {
                            subFields.put(subField.getKey(), Map.of("type", getPropertyType(subField.getValue())));
                        }
                        fieldInfo.put("fields", subFields);
                    }
                }
                
                // 如果是scaled_float类型，可能还有scaling_factor属性
                if ("scaled_float".equals(fieldType) && property.scaledFloat() != null) {
                    if (property.scaledFloat().scalingFactor() != null) {
                        fieldInfo.put("scaling_factor", property.scaledFloat().scalingFactor());
                    }
                }
                
                // 如果是date类型，可能还有format属性
                if ("date".equals(fieldType) && property.date() != null) {
                    if (property.date().format() != null) {
                        fieldInfo.put("format", property.date().format());
                    }
                }
                
                simplified.put(fieldName, fieldInfo);
            } catch (Exception e) {
                logger.warn("处理字段 {} 时出错，使用默认text类型: {}", fieldName, e.getMessage());
                simplified.put(fieldName, Map.of("type", "text"));
            }
        }
        
        return simplified;
    }
    
    /**
     * 获取属性的类型字符串
     * @param property 属性对象
     * @return 类型字符串
     */
    private String getPropertyType(Property property) {
        try {
            // 使用Property的_kind()方法来安全地检查属性类型
            switch (property._kind()) {
                case Text:
                    return "text";
                case Keyword:
                    return "keyword";
                case Integer:
                    return "integer";
                case Long:
                    return "long";
                case Date:
                    return "date";
                case Boolean:
                    return "boolean";
                case ScaledFloat:
                    return "scaled_float";
                case Float:
                    return "float";
                case Double:
                    return "double";
                case Object:
                    return "object";
                case Nested:
                    return "nested";
                case GeoPoint:
                    return "geo_point";
                case GeoShape:
                    return "geo_shape";
                case Completion:
                    return "completion";
                case Ip:
                    return "ip";
                case TokenCount:
                    return "token_count";
                default:
                    return "text"; // 默认类型
            }
        } catch (Exception e) {
            // 如果无法确定类型，则默认为text
            logger.warn("无法确定属性类型，使用默认类型text: {}", e.getMessage());
            return "text";
        }
    }
}